package com.zmm.study.blog.utils;

import com.alibaba.fastjson.JSON;
import com.google.gson.Gson;
import com.zmm.common.base.model.ResultCode;
import com.zmm.common.constant.NumberConstant;
import com.zmm.common.exception.handle.BusinessException;
import com.zmm.study.blog.config.MyElasticSearchConfig;
import com.zmm.study.blog.constant.IndexName;
import com.zmm.study.blog.model.SearchParam;
import com.zmm.study.blog.test.es.model.Article;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.IndicesClient;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.core.CountRequest;
import org.elasticsearch.client.core.CountResponse;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * description:
 * name EsHandleUtil
 *
 * @author Zebulon
 * date By 2022-01-20 17:31:46
 */
@Slf4j
public class EsHandleUtil {

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    @Resource
    private Gson gson;


    /**
     * 指定 [索引名]判断是否存在
     * @author: 900045
     * @date: 2021-01-29 11:37:50
     * @throws IOException
     * @return: void
     **/
    public boolean indexExists(String indexName,String id){
        boolean exists = false;
        if (ObjectUtils.isEmpty(id)){
            GetIndexRequest request = new GetIndexRequest(indexName);
            IndicesClient indices = restHighLevelClient.indices();
            try {
                exists = indices.exists(request, RequestOptions.DEFAULT);
            } catch (IOException e) {
                log.error("判断{}索引是否存在 异常 {}",indexName,e);
                e.printStackTrace();
            }
        } else {
            GetRequest getRequest = new GetRequest(indexName, id);
            try {
                exists = restHighLevelClient.exists(getRequest, RequestOptions.DEFAULT);
            } catch (IOException e) {
                log.error("判断{}索引是否存在 异常 {}",indexName,e);
                e.printStackTrace();
            }
        }
        return exists;
    }

    /**
     * 根据索引名称创建索引
     * @author: Administrator
     * @date: 2021-02-17 16:52:55
     * @throws IOException
     * @param indexName: 索引名
     * @return: boolean
     **/
    public boolean createIndex(String indexName){
        CreateIndexRequest createIndexRequest = new CreateIndexRequest(indexName);
        // 设置分片数量
        createIndexRequest.settings(
                Settings.builder()
                        .put("index.number_of_shards",1)
                        .put("index.number_of_replicas",0));
        IndicesClient indices = restHighLevelClient.indices();
        CreateIndexResponse response = null;
        try {
            response = indices.create(createIndexRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("创建索引 {} 失败!!{}",indexName,e);
        }
        //提取有用的响应数据
        log.info("createIndex----响应结果:{},isAcknowledged:{}",response.toString(),response.isAcknowledged());
        return response.isAcknowledged();
    }


    /**
     * 给es 文档中添加数据
     * @author: 900045
     * @date: 2021-02-05 18:47:28
     * @throws IOException
     * @param indexName:   文档 名称
     * @param jsonString: 	数据 (json字符串)
     * @return: void
     **/
    public boolean addIndexData(String indexName,String jsonString,Object object){
        boolean exists = this.indexExists(indexName,null);
        boolean sign = true;
        if (!exists){
            boolean index = createIndex(indexName);
            if (index){
                sign = true;
                log.error("索引:{}创建成功~~~",indexName);
            }else {
                sign = false;
                log.error("索引:{}创建失败~~~",indexName);
            }
        }
        // 如果 sign 是 false 则不执行 后续操作
        if (!sign){
            return false;
        }
        IndexRequest indexRequest = new IndexRequest(indexName);
        indexRequest.id(object.toString());

        // 要保存的数据
        indexRequest.source(jsonString, XContentType.JSON);
        // 执行操作
        IndexResponse index = null;
        try {
            index = restHighLevelClient.index(indexRequest, MyElasticSearchConfig.COMMON_OPTIONS);
        } catch (IOException e) {
            log.error("{}索引中添加数据 失败!!!{}",indexName,e);
        }
        //提取有用的响应数据
        log.info("addIndexData---响应{}",index.toString());

        if (index.getShardInfo().getFailed() > 0){
            // 处理潜在的故障
            log.info("向{}索引中添加数据failed !!!",indexName);
            return false;
        } else {
            log.info("向{}索引中添加数据success !!!",indexName);
            return true;
        }
    }

    /**
     * 更新一个索引文档
     * @author: 900045
     * @date: 2021-02-22 11:55:19
     * @throws
     * @param indexName:
     * @param jsonString:
     * @param object:
     * @return: boolean
     **/
    public boolean updateIndex(String indexName, String jsonString, Object object){

        UpdateRequest updateRequest = new UpdateRequest(indexName,object.toString());
        updateRequest.retryOnConflict(3);
        updateRequest.doc(jsonString, XContentType.JSON);
        UpdateResponse update = null;
        try{
            log.info("{}索引,更新数据,rq:{}", indexName,jsonString);
            update = restHighLevelClient.update(updateRequest, MyElasticSearchConfig.COMMON_OPTIONS);
        }catch (IOException e) {
            log.error("es update failed{}",e);
        }
        if (update.status() == RestStatus.OK){
            return true;
        }
        return false;
    }

    /**
     * 删除索引
     * @author: 900045
     * @date: 2021-02-07 10:28:53
     * @throws
     * @param indexName: 索引名
     * @return: boolean
     **/
    public boolean deleteIndex(String indexName){
        DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(indexName);
        IndicesClient indices = restHighLevelClient.indices();
        AcknowledgedResponse delete = null;
        try {
            delete = indices.delete(deleteIndexRequest, MyElasticSearchConfig.COMMON_OPTIONS);
        } catch (IOException e) {
            log.error("删除{}索引 失败!!! {}",indexName,e);
        }
        boolean acknowledged = delete.isAcknowledged();
        log.info("deleteIndex-->索引{}删除成功 isAcknowledged:{}",indexName,acknowledged);
        return acknowledged;
    }



    /**
     * 根据条件查询 索引中文档的数量
     * @author: 900045
     * @date: 2021-02-07 11:24:07
     * @throws
     * @param searchParam: 查询条件
     * @return: int
     **/
    public int countIndexNumber(SearchParam searchParam, Integer source){
        CountRequest countRequest = new CountRequest(searchParam.getIndexName());
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        // 动态构建出查询需要的 DSL 语句
        switch (source){
            case 0:
                //默认 goods 模块
                conditionQuery(searchParam,searchSourceBuilder);
                break;
            case 1:
                // platform
                platformConditionQuery(searchParam,searchSourceBuilder);
                break;
        }

        countRequest.source(searchSourceBuilder);
        CountResponse countResponse = null;
        try {
            countResponse = restHighLevelClient.count(countRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("es select count failed{}",e);
        }
        long countResponseCount = countResponse.getCount();
        if (countResponseCount == 0){
            return 0;
        }
        int count = Long.valueOf(countResponseCount).intValue();
        log.error("查询到的数量:{}",count);
        return count;
    }

    /**
     *  查询条件封装
     * @author: 900045
     * @date: 2021-02-07 14:33:48
     * @throws
     * @param searchParam: 查询参数
     * @param sourceBuilder: 封装的 DSL 语句 对象
     *   matchQuery：会将搜索词分词，再与目标查询字段进行匹配，若分词中的任意一个词与目标字段匹配上，则可查询到
     *   termQuery：不会对搜索词进行分词处理，而是作为一个整体与目标字段进行匹配，若完全匹配，则可查询到
     * @return: org.elasticsearch.search.builder.SearchSourceBuilder
     **/
    private SearchSourceBuilder conditionQuery(SearchParam searchParam, SearchSourceBuilder sourceBuilder) {
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        String indexName = searchParam.getIndexName();
        switch (indexName){
            case IndexName.CAROUSEL:

                break;
            case IndexName.ARTICLE:

                break;
            default:
        }

        sourceBuilder.query(boolQueryBuilder);
        String s = sourceBuilder.toString();
        log.debug("{}索引{}构建的DSL语句--->{}",searchParam.getSource()+":前台",indexName,s);
        return sourceBuilder;
    }


    /**
     * 分页数据
     * @author: 900045
     * @date: 2021-02-08 17:03:05
     * @throws
     * @param size: 查询到的数量
     * @param sourceBuilder: 封装的 DSL 语句
     * @param searchParam: 查询条件
     * @return: void
     **/
    private void page(int size,SearchSourceBuilder sourceBuilder,SearchParam searchParam){
        // doPage 为true 则 表示 需要分页查询
        if (searchParam.isDoPage()){
            sourceBuilder.from((searchParam.getCurrentPage()-1) * searchParam.getLimit());
            sourceBuilder.size(searchParam.getLimit());
        } else {
            //表示查询全部(不分页)
            //查询全部的数据(应该es 查询默认只查10条,如果结果大于10条 就必须指定 size)
            sourceBuilder.from(0);
            sourceBuilder.size(size);
        }
    }

    /**
     * orderingFloorByThemeCode / findFloorByTemplate / findModuleByTemplate 包装排序数据
     * @author: 900045
     * @date: 2021-02-10 11:11:19
     * @throws
     * @param sourceBuilder: 封装的 DSL 语句
     * @return: void
     **/
    private void setOrderingFloorByThemeCode(SearchSourceBuilder sourceBuilder){
        sourceBuilder.sort("ordering", SortOrder.ASC);
        sourceBuilder.sort("floorOrder",SortOrder.DESC);
        sourceBuilder.sort("beginTime",SortOrder.DESC);
    }

    /**
     * 专题模板的 排序
     * @author: 900045
     * @date: 2021-02-19 15:13:01
     * @throws
     * @param sourceBuilder:
     * @return: void
     **/
    private void setSortNumSubjectTempAsc(SearchSourceBuilder sourceBuilder){
        sourceBuilder.sort("sortNum",SortOrder.ASC);
    }

    /**
     * 专题排序
     * @author: 900045
     * @date: 2021-02-19 15:18:44
     * @throws
     * @param sourceBuilder:
     * @return: void
     **/
    private void setSortNumSubjectDesc(SearchSourceBuilder sourceBuilder){
        sourceBuilder.sort("sortNum",SortOrder.DESC);
    }

    /**
     * getBannerAll / getSliderAll
     * @author: 900045
     * @date: 2021-02-10 11:16:44
     * @throws
     * @param sourceBuilder: 封装的 DSL 语句
     * @return: void
     **/
    private void setOrderingGetSliderAllDesc(SearchSourceBuilder sourceBuilder){
        sourceBuilder.sort("ordering",SortOrder.DESC);
    }

    private void setOrderingAdvertAsc(SearchSourceBuilder sourceBuilder){
        sourceBuilder.sort("ordering",SortOrder.ASC);
    }

    /**
     * 执行 es 查询 并返回结果
     * @author: 900045
     * @date: 2021-02-07 14:33:25
     * @throws
     * @param searchRequest: 执行查询后返回的结果
     * @return: org.elasticsearch.action.search.SearchResponse
     **/
    public SearchResponse executeSearchByEs(SearchRequest searchRequest){
        try {
            // 2.执行检索请求
            SearchResponse response = restHighLevelClient.search(searchRequest, MyElasticSearchConfig.COMMON_OPTIONS);
            // 3.分析响应数据封装成我们需要的格式
            return response;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取 SearchResponse 响应结果
     * @author: 900045
     * @date: 2021-02-17 17:14:19
     * @throws
     * @param searchParam: 查询条件
     * @param sort: 排序选择
     * @return: org.elasticsearch.action.search.SearchResponse
     **/
    public SearchResponse backSearchResponse(SearchParam searchParam, Integer sort){
        SearchResponse response = this.search(searchParam,sort);
        log.error("索引名:{} --- value:{}",searchParam.getIndexName(),response.getHits().getTotalHits());
        // 不需要判断是否存在空指针
        return response;
    }

    /**
     * 执行 es 查询
     * @author: Administrator
     * @date: 2021-02-17 17:16:11
     * @throws
     * @param searchParam: 查询条件
     * @param sort: 排序选择
     * @return: org.elasticsearch.action.search.SearchResponse
     **/
    public SearchResponse search(SearchParam searchParam,Integer sort){
        // 1.查询其数量 如果size 为0 直接返回 null,不为 0 继续查询相关数据
        Integer source = searchParam.getSource();
        int size = countIndexNumber(searchParam,source);
        if (size == 0){
            log.debug("{}:文档中没有数据!!!",searchParam.getIndexName());
        }
        // 2.动态构建出查询需要的 DSL 语句
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        switch (source){
            case 0:
                //默认 goods 模块
                goodsModuleConditionQuery(searchParam,sourceBuilder,sort);
                break;
            case 1:
                // platform
                platformModuleConditionQuery(searchParam,sourceBuilder,sort);
                break;
        }

        // TODO setSortOrder 排序
        //setSortOrder(searchParam,sourceBuilder);
        // 4.分页查询
        page(size,sourceBuilder,searchParam);
        // 5.封装查询请求参数 SearchRequest
        SearchRequest searchRequest = new SearchRequest(new String[]{searchParam.getIndexName()}, sourceBuilder);
        return executeSearchByEs(searchRequest);
    }

    /**
     * goods 模块的请求 参数 与 排序封装
     * @author: 900045
     * @date: 2021-02-22 11:01:28
     * @throws
     * @param searchParam:
     * @param sourceBuilder:
     * @param sort:
     * @return: void
     **/
    private void goodsModuleConditionQuery(SearchParam searchParam, SearchSourceBuilder sourceBuilder,Integer sort){
        conditionQuery(searchParam,sourceBuilder);
        // 3.根据 sort 排序
        switch (sort){
            // sort 为 0 表示 无排序
            case 1:setOrderingFloorByThemeCode(sourceBuilder);break;
            case 2:setOrderingGetSliderAllDesc(sourceBuilder);break;
            case 3:setSortNumSubjectTempAsc(sourceBuilder);break;
            case 4:setSortNumSubjectDesc(sourceBuilder);break;
            case 5:
                break;
            case 6:
                setOrderingAdvertAsc(sourceBuilder);break;
            case 7:
                setOrderingArticle(sourceBuilder);
                break;
            default:

        }
    }

    private void setOrderingArticle(SearchSourceBuilder sourceBuilder) {
        // 阅读量 降序 从大到小
        sourceBuilder.sort("readSize", SortOrder.DESC);
        sourceBuilder.sort("shareSize",SortOrder.DESC);
        sourceBuilder.sort("collectSize",SortOrder.DESC);
        sourceBuilder.sort("ordering",SortOrder.DESC);
    }

    private void platformModuleConditionQuery(SearchParam searchParam, SearchSourceBuilder sourceBuilder,Integer sort){
        platformConditionQuery(searchParam, sourceBuilder);
        switch (sort){
            case 1:
                // 创建时间降序 从大到小
                sourceBuilder.sort("createTime",SortOrder.DESC);
                break;
            case 2:
                setSortNumSubjectDesc(sourceBuilder);
                break;
            default:
        }
    }

    private SearchSourceBuilder platformConditionQuery(SearchParam searchParam, SearchSourceBuilder sourceBuilder) {
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        String indexName = searchParam.getIndexName();
        switch (indexName){
            case IndexName.TEST_INDEX:
                break;
        }
        sourceBuilder.query(boolQueryBuilder);
        String s = sourceBuilder.toString();
        log.debug("{}索引{}构建的DSL语句--->{}",searchParam.getSource()+":后台",indexName,s);
        return sourceBuilder;
    }

    /**
     * 批量将数据导入 es 中
     * @author: Administrator
     * @date: 2021-02-17 19:45:45
     * @throws BusinessException
     * @param objectList: 需要保存的数据集合 用 Object 接收
     * @param indexName: 索引名
     * @return: boolean
     **/
    public boolean bulkSaveDataToEs(Object objectList,String indexName) throws BusinessException {
        boolean     index       = createIndex(indexName);
        if (!index){
            log.error("[{}]索引,创建失败",indexName);
            return false;
        }
        BulkRequest bulkRequest = new BulkRequest();

        switch (indexName){
            case IndexName.ARTICLE:
                List<Article> articleList = JSON.parseArray(JSON.toJSONString(objectList), Article.class);
                // 1.构造保存请求
                for (Article article : articleList) {
                    IndexRequest indexRequest = new IndexRequest(IndexName.ARTICLE);
                    packageIndexRequest(indexRequest,Long.valueOf(article.getId()).toString(),article);
                    /**
                     * Settings.builder()
                     * 						.put("index.number_of_shards",1)
                     * 						.put("index.number_of_replicas",0))
                     */
                    bulkRequest.add(indexRequest);
                }
                break;
        }
        BulkResponse bulk;
        try {
            bulk = restHighLevelClient.bulk(bulkRequest, MyElasticSearchConfig.COMMON_OPTIONS);
        } catch (IOException e) {
            throw new BusinessException(ResultCode.BULK_SAVE_INDEX_FAIL);
        }
        // 是否存在错误 没有错误为 false 有错误为true
        boolean hasFailures = bulk.hasFailures();
        List<String> stringList = Arrays.stream(bulk.getItems()).map(goods ->goods.getId()).collect(Collectors.toList());
        log.error("批量保存[{}]索引,返回数据:{}",indexName,stringList);
        return !hasFailures;
    }

    /**
     * 包装 IndexRequest 对象
     * @author: 900045
     * @date: 2021-02-09 11:38:36
     * @throws
     * @param indexRequest: IndexRequest 索引请求对象
     * @param id: 索引id
     * @param object: 目标数据
     * @return: void
     **/
    private void packageIndexRequest(IndexRequest indexRequest,String id,Object object){
        indexRequest.id(id);
        String jsonString = gson.toJson(object);
        indexRequest.source(jsonString, XContentType.JSON);
    }


    /**
     * 从 es 返回结果 hits 中 进行包装 List<Advert>
     * @author: 900045
     * @date: 2021-02-10 11:46:18
     * @throws
     * @param hits: response.getHits().getHits()的返回值
     * @return: java.util.List<com.social.module.goods.es.document.Advert>
     **/
	/*public List<Advert> packageAdvert(SearchHit[] hits){
		List<Advert> adverts = new ArrayList<>();
		for (SearchHit searchHit : hits){
			String sourceAsString = searchHit.getSourceAsString();
			adverts.add(JSON.parseObject(sourceAsString,Advert.class));
		}
		return adverts;
	}*/

    /**
     * 封装基本查询分页条件
     * @author: 900045
     * @date: 2021-04-12 16:45:56
     * @throws
     * @param indexName:
     * @param map:
     * @return: com.sandy.service.search.param.SearchParam
     **/
    public SearchParam packageSearchParam(String indexName, Map<String, Object> map){
        SearchParam searchParam = new SearchParam(indexName);
        searchParam.setSource(NumberConstant.ZERO);
        // 分页
        searchParam.setDoPage(true);
        searchParam.setLimit(Integer.valueOf(map.get("limit").toString()));
        searchParam.setCurrentPage(Integer.valueOf(map.get("currentPage").toString()));
        return searchParam;
    }
}
